using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using Cysharp.Threading.Tasks;
using Framework.Interface;
using Framework.Network;
using Framework.Singleton;
using NetMessage;
using UnityEngine;

namespace Framework.Manager
{
    public static class Serializer
    {
        public static Message Decode<Message>(byte[] data,int index, int count)
        {
            using (MemoryStream memory = new MemoryStream(data, index, count))
            {
                //Type t = Type.GetType(Message);
                return (Message)ProtoBuf.Serializer.Deserialize(typeof(Message), memory);
            }
        }

        public static byte[] Encode<Message>(Message msg)
        {
            using (MemoryStream memory = new MemoryStream())
            {
                ProtoBuf.Serializer.Serialize(memory, msg);
                return memory.ToArray();
            }
        }
    }
    
    public class NetworkManager : MonoSingleton<NetworkManager>, IMonoManager
    {
        public Action<long, string> OnError;
        
        private readonly NetSocket socket = new ();
        private readonly Dictionary<int, Action<SocketMessage>> actions = new ();
        private readonly Dictionary<int, UniTaskCompletionSource<SocketMessage>> tasks = new ();
        private readonly Dictionary<string, ushort> protocolNameIdMap = new ();

        public async UniTask Initialize(Transform parent)
        {
            transform.SetParent(parent);
            await UniTask.CompletedTask;
            GetProtocolName2Id();
        }

        public void Shutdown()
        {
            socket.CloseAll();
        }

        private void GetProtocolName2Id()
        {
            string[] enumNames = Enum.GetNames(typeof(CmdCode));
            for (int i = 0; i < enumNames.Length; i++)
            {
                string protocolName = enumNames[i];
                protocolNameIdMap.Add(protocolName, Convert.ToUInt16(Enum.Parse<CmdCode>(protocolName)));
            }
        }

        public SocketMessage Connect(int connectType, string ip, int port, SocketProtocolType protocolType)
        {
            return socket.Connect(connectType, ip, port, protocolType);
        }

        public UniTask<SocketMessage> ConnectAsync(int connectType, string ip, int port, SocketProtocolType protocolType)
        {
            UniTaskCompletionSource<SocketMessage> task = new UniTaskCompletionSource<SocketMessage>();
            tasks.Add(connectType, task);
            
            socket.ConnectAsync(connectType, ip, port, protocolType);
            
            return task.Task;
        }

        public async UniTask<T> Call<T>(int connectType, object request)
        {
            UniTaskCompletionSource<SocketMessage> task = new UniTaskCompletionSource<SocketMessage>();

            Type responseType = typeof(T);
            ushort resId = protocolNameIdMap[responseType.Name];
            tasks[resId] = task;

            NetBuffer buffer = new NetBuffer();
            Type requestType = request.GetType();
            ushort reqId = protocolNameIdMap[requestType.Name];
            buffer.Write(reqId);

            byte[] sdata = Serializer.Encode(request);
            if (sdata != null)
            {
                buffer.Write(sdata, 0, sdata.Length);
                Send(connectType, buffer);
            }
            else
            {
                return default(T);
            }

            SocketMessage response = await task.Task;

            return Serializer.Decode<T>(response.Data.Data, response.Data.Index, response.Data.Count);
        }
        
        public void Send(int connectType, string data)
        {
            socket.Send(connectType, Encoding.Default.GetBytes(data));
        }

        public void Send(int connectType, byte[] data)
        {
            socket.Send(connectType, data);
        }
        
        public void Send(int connectType, NetBuffer data)
        {
            socket.Send(connectType, data);
        }

        public bool Send<T>(int connectType, T request)
        {
            byte[] bytes = Serializer.Encode(request);
            if (bytes == null)
            {
                OnError(0, $"Send Message {request.ToString()}, Serialize error");
                return false;
            }

            string protocolName = request.GetType().Name;
            ushort id = protocolNameIdMap[protocolName];
            NetBuffer buffer = new NetBuffer();
            buffer.Write(id);
            buffer.Write(bytes, 0, bytes.Length);
            Send(connectType, buffer);

            return true;
        }

        public void Close(int connectType)
        {
            socket.Close(connectType);
        }

        public void Register<T>(Action<T> handler)
        {
            Type type = typeof(T);
            ushort msgId = protocolNameIdMap[type.Name];
            actions[msgId] = msg =>
            {
                T response = Serializer.Decode<T>(msg.Data.Data, msg.Data.Index, msg.Data.Count);

                if (handler != null)
                    handler(response);
            };
        }

        private void Dispatch(SocketMessage message)
        {
            switch (message.MessageType)
            {
                case SocketMessageType.Connect:
                {
                    if (tasks.TryGetValue(message.ConnectId, out UniTaskCompletionSource<SocketMessage> tcs))
                    {
                        tasks.Remove(message.ConnectId);
                        tcs.TrySetResult(message);
                    }

                    break;
                }
                case SocketMessageType.Close:
                {
                    if (message.SessionId != 0)
                    {
                        if (tasks.TryGetValue(message.SessionId, out UniTaskCompletionSource<SocketMessage> tcs))
                        {
                            tasks.Remove(message.SessionId);
                            tcs.TrySetResult(message);
                        }
                    }
                    break;
                }
                case SocketMessageType.Message:
                {
                    if (message.SessionId != 0)
                    {
                        if (tasks.TryGetValue(message.SessionId, out UniTaskCompletionSource<SocketMessage> tcs))
                        {
                            tasks.Remove(message.SessionId);
                            tcs.TrySetResult(message);
                        }
                        else
                        {
                            OnError(0, $"session{message.SessionId} not register!");
                        }
                    }

                    
                    var msgId = message.Data.ReadUInt16();
                    
                    if (actions.TryGetValue(msgId, out Action<SocketMessage> action))
                    {
                        action(message);
                    }
                    else
                    {
                        if (tasks.TryGetValue(msgId, out UniTaskCompletionSource<SocketMessage> tcs))
                        {
                            tasks.Remove(msgId);
                            tcs.TrySetResult(message);
                        }
                        else
                        {
                            OnError(0, $"message{message.GetType()} not register!!{message.Data.GetString()}");
                        }
                    }
                    
                    break;
                }
            }
        }

        private void Update()
        {
            if (socket == null)
                return;
            
            while (true)
            {
                SocketMessage message = socket.PopMessage();
                if (message == null)
                {
                    break;
                }
                
                Dispatch(message);
            }
        }
    }
}